<?php


namespace App\Services;


use App\Exceptions\ApiException;
use App\Exports\BookTitleExport29;
use App\Exports\BookTitleExport8;
use App\Exports\BookTitleExportLY;
use App\Models\Company;
use App\Models\PartCompany;
use App\Models\Project;
use App\Models\ProjectClassify;
use App\Services\ProjectService;
use Illuminate\Support\Facades\Storage;
use PhpOffice\PhpWord\Exception\CopyFileException;
use PhpOffice\PhpWord\Exception\CreateTemporaryFileException;
use PhpOffice\PhpWord\TemplateProcessor;
use Symfony\Component\Process\Process;

class ExportService
{
    /**
     * 导出案卷
     *
     * @param $partCompanyId
     * @param $projectClassifyId
     * @param $bookTitleIds
     * @return array
     * @author: lxk
     * @date: 2025-07-15 11:52
     */
    public function exportBookTitle($partCompanyId, $projectClassifyId, $bookTitleIds): array
    {
        $fileName      = self::getDir($partCompanyId, $projectClassifyId, $bookTitleIds, 4);
        $filePath      = "public/excels/$fileName" . '.xlsx';
        $combinationId = (new ProjectService())->getDossierCombination($partCompanyId);
        switch ($combinationId) {
            case 2: //8号文格式
                (new BookTitleExport8($bookTitleIds))->store($filePath);
                break;
            case 3: //兰渝格式
                (new BookTitleExportLY($bookTitleIds))->store($filePath);
                break;
            default: //29号文格式
                (new BookTitleExport29($bookTitleIds))->store($filePath);
                break;
        }
        $filePath    = str_replace('public', 'storage', $filePath);
        $downloadUrl = config('site.domain') . $filePath;
        $fileInfo    = pathinfo($downloadUrl);

        CommonService::addProjectActionLog('导出案卷目录', 0, $partCompanyId, $projectClassifyId);

        return [
            'basename'     => $fileInfo['basename'],
            'download_url' => $downloadUrl
        ];
    }

    /**
     * 导出备考表
     *
     * @param $partCompanyId
     * @param $projectClassifyId
     * @param $bookTitleIds
     * @return array|string
     * @throws ApiException
     * @throws CopyFileException
     * @throws CreateTemporaryFileException
     * @author: lxk
     * @date: 2025-07-26 16:29
     */
    public function exportAppendix($partCompanyId, $projectClassifyId, $bookTitleIds): array
    {
        $fileDir = self::getDir($partCompanyId, $projectClassifyId, $bookTitleIds, 4);
        $docPath = "words/appendixes/{$fileDir}"; //备考表目录
        CommonService::createDir($docPath);
        $absStorageDir       = storage_path('app/public/' . $docPath);
        $bookCatalogInstance = CommonService::getBookCatalogInstance($partCompanyId, $projectClassifyId);
        $combinationId       = (new ProjectService())->getDossierCombination($partCompanyId); //获取对应的案卷模板组合
        foreach ($bookTitleIds as $bookTitleId) {
            $info  = $bookCatalogInstance->where('book_title_id', $bookTitleId)->where('remark', '!=', '')->first();
            $annex = !empty($info) ? 1 : 0;
            $this->createAppendixDoc($combinationId, $partCompanyId, $projectClassifyId, $bookTitleId, $absStorageDir, $annex); //备考表
        }
        CommonService::addProjectActionLog("导出{$fileDir}", $bookTitleIds[0], $partCompanyId, $projectClassifyId);
        $isHaveRemarks = $bookCatalogInstance->whereIn('book_title_id', $bookTitleIds)->where('remark', '!=', '')->first();
        $dirStorage    = 'storage/' . $docPath;
        if ($isHaveRemarks && $combinationId == 1) { //如果有附件
            $result1            = self::mergeWord($dirStorage, 'appendixes'); //合并备考表
            $dirStorage         = str_replace('备考表', '卷内备注', $dirStorage);
            $result2            = self::mergeWord($dirStorage, 'appendixes'); //合并备注附件
            $annexAndRemarksDir = str_replace('备考表', '卷内备注和附件', $docPath);
            CommonService::createDir($annexAndRemarksDir); //创建一个文件夹，存放合并的两个文件，准备zip压缩
            //把合并的备考表移动进来
            Storage::move("public/words/appendixes/{$result1['basename']}", "public/{$annexAndRemarksDir}/{$result1['basename']}");
            //把合并的附件移动进来
            Storage::move("public/words/appendixes/{$result2['basename']}", "public/{$annexAndRemarksDir}/{$result2['basename']}");
            //打包成zip文件
            $downloadUrl = CommonService::createZip('storage/' . $annexAndRemarksDir);
            return [
                'basename'     => pathinfo($downloadUrl)['basename'],
                'download_url' => $downloadUrl
            ];
        } else { //直接合并备考表后就返回
            return $this->mergeWord($dirStorage, 'appendixes');
        }
    }

    /**
     * 创建一个备考表文件
     *
     * @param $partCompanyId
     * @param $projectClassifyId
     * @param $bookTitleId
     * @param $dirStorage
     * @param int $annex
     * @return string
     * @author: lxk
     * @date: 2025-07-17 14:35
     */
    public function createAppendixDoc($combinationId, $partCompanyId, $projectClassifyId, $bookTitleId, $absStorageDir, int $annex = 0): string
    {
        $bookTitle = BookTitleService::getBookTitleInfo($partCompanyId, $projectClassifyId, $bookTitleId);
        $projectId = PartCompany::query()->where('id', $partCompanyId)->value('project_id');
        switch ($combinationId) { //通过判断，使用项目指定备考表模板
            case 2: //8号文
                $templateProcessor = new TemplateProcessor('storage/words/doc_template/appendix2.docx');
                break;
            case 3: //兰渝格式
                $templateProcessor = new TemplateProcessor('storage/words/doc_template/appendix2.docx');
                break;
            default: //29号文
                if ($projectId == 44) { //注：原线路名称为新建川南城际铁路自贡至宜宾线。
                    $templateProcessor = new TemplateProcessor('storage/words/doc_template/appendix3.docx');
                } elseif ($projectId == 33) { //注：成昆铁路峨眉至米易段扩能工程。
                    $templateProcessor = new TemplateProcessor('storage/words/doc_template/appendix4.docx');
                } else {
                    $templateProcessor = new TemplateProcessor('storage/words/doc_template/appendix1.docx');
                }
        }
        $templateProcessor->setValue('archives_no', $bookTitle->archives_no); //档案号
        $templateProcessor->setValue('mixed_no', $bookTitle->mixed_no); //互见号
        $templateProcessor->setValue('total', $bookTitle->page_num); //材料总数  页次=文字材料+蓝图
        $templateProcessor->setValue('text', $bookTitle->word_material); //文字材料
        $templateProcessor->setValue('image', $bookTitle->base_map + $bookTitle->blueprint); //图片材料
        $templateProcessor->setValue('photo', 0); //照片
        $catalogRemarks = '';
        if ($combinationId == 2 || $combinationId == 3 || $combinationId == 4) { //8号文和兰渝，不额外增加附件
            $catalogRemarkArr = $this->getCatalogRemarks($partCompanyId, $projectClassifyId, $combinationId, $bookTitle);
            $enterChar        = "<w:br/>";
            $separateChar     = "{$enterChar}";
            $catalogRemarks   = implode($separateChar, $catalogRemarkArr) ?? '';
        }
        $templateProcessor->setValue('remarks', $catalogRemarks);
        $fileName = "{$absStorageDir}/{$bookTitle->serial_no}";
        $savePath = "{$fileName}.docx";
        $filePath = str_replace('storage/', '', $savePath);
        if (Storage::exists('/public/' . $filePath)) Storage::delete('/public/' . $filePath);
        $templateProcessor->saveAs($savePath); //另存为
        if ($combinationId == 1) { //29号文，如果卷内有备注，备注生成一个单独的word附件
            if ($annex) { //注：成昆铁路峨眉至米易段扩能工程备注少，不需要独立
                $this->createAppendixAnnex($partCompanyId, $projectClassifyId, $bookTitle, $combinationId, $absStorageDir);
            }
        }

        return public_path() . "/{$savePath}"; //保存的路径
    }

    /**
     * 生成卷内备考表附件，如果卷内有备注，把卷内备注导出来，单独形成文件作为备考表的附件用
     *
     * @param $partCompanyId
     * @param $projectClassifyId
     * @param $bookTitle
     * @param $combinationId
     * @param $dirStorage
     * @author: lxk
     * @date: 2025-07-17 14:34
     */
    public function createAppendixAnnex($partCompanyId, $projectClassifyId, $bookTitle, $combinationId, $absStorageDir)
    {
        $catalogRemarkArr = $this->getCatalogRemarks($partCompanyId, $projectClassifyId, $combinationId, $bookTitle);
        if (!empty($catalogRemarkArr)) { //如果卷内有备注，才生成备考表附件
            $dirStorage = substr($absStorageDir, strpos($absStorageDir, 'words'));
            $dirStorage = str_replace('备考表', '卷内备注', $dirStorage);
            CommonService::createDir($dirStorage); //创建文件夹
            $enterChar         = "<w:br/>";
            $separateChar      = "{$enterChar}";
            $catalogRemarks    = implode($separateChar, $catalogRemarkArr) ?? '';
            $templateProcessor = new TemplateProcessor('storage/words/doc_template/appendix_annex.docx');
            $templateProcessor->setValue('archives_no', $bookTitle->archives_no); //卷内备注
            $templateProcessor->setValue('remarks', $enterChar . $catalogRemarks); //卷内备注

            $fileName = "storage/{$dirStorage}/{$bookTitle->serial_no}";
            $savePath = "{$fileName}.docx";
            $filePath = str_replace('storage/', '', $savePath);
            if (Storage::exists('/public/' . $filePath)) Storage::delete('/public/' . $filePath);
            $templateProcessor->saveAs($savePath); //另存为
        }
    }

    /**
     * 获取一个案卷的卷内备注
     *
     * @param $partCompanyId
     * @param $projectClassifyId
     * @param $combinationId
     * @param $bookTitle
     * @return array
     * @author: lxk
     * @date: 2025-07-17 14:34
     */
    public function getCatalogRemarks($partCompanyId, $projectClassifyId, $combinationId, $bookTitle): array
    {
        $projectId           = PartCompany::query()->where('id', $partCompanyId)->value('project_id');
        $bookCatalogInstance = CommonService::getBookCatalogInstance($partCompanyId, $projectClassifyId);
        $remarks             = $bookCatalogInstance->where('book_title_id', $bookTitle->id)->select('serial_no', 'title', 'remark')->orderBy('serial_no')->get();
        $catalogRemarkArr    = [];
        if ($remarks->isNotEmpty()) {
            $remarks = $remarks->toArray();
            $number  = 0;
            foreach ($remarks as $val) {
                if ($val['remark']) {
                    if ($combinationId == 2 || $combinationId == 3) { //8号文，兰渝
                        $catalogRemarkArr[] = "{$val['remark']}";
                    } else {
                        $number     += 1;
                        $splitTitle = str_replace('检验批质量验收记录表、施工记录表、试验报告', '', $val['title']);
                        if ($projectId == 33) { //峨米项目，因为备注写的很清晰，所以不需要代上卷内题名了
                            $catalogRemarkArr[] = "{$number}、{$val['remark']}";
                        } else {
                            $catalogRemarkArr[] = "{$number}、“{$splitTitle}” {$val['remark']}";
                        }
                    }
                }
            }
        }

        return $catalogRemarkArr;
    }

    /**
     * 导出封面
     *
     * @param $partCompanyId
     * @param $projectClassifyId
     * @param $bookTitleIds
     * @return array
     * @throws ApiException
     * @author: lxk
     * @date: 2025-07-17 14:46
     */
    public function exportCover($partCompanyId, $projectClassifyId, $bookTitleIds): array
    {
        $fileDir = self::getDir($partCompanyId, $projectClassifyId, $bookTitleIds, 5);
        $docPath = "words/covers/{$fileDir}"; //封面
        CommonService::createDir($docPath);
        $absStorageDir = storage_path('app/public/' . $docPath);
        foreach ($bookTitleIds as $bookTitleId) {
            $this->createCoverDoc($partCompanyId, $projectClassifyId, $bookTitleId, $absStorageDir);
        }
        $pythonEnv = self::getPythonEnv();
        $process   = new Process([$pythonEnv, 'python_script/29_cover.py', $absStorageDir]); //交给python进行合成

        CommonService::addProjectActionLog("导出{$fileDir}", $bookTitleIds[0], $partCompanyId, $projectClassifyId);

        return $this->dealByPython($process, '备考表');
    }

    /**
     * 在UTF-8编码下,一个汉字占3个字节，strlen($project->title) 的长度除以3是汉字长度
     * mb_substr是中文截取
     */
    public function createCoverDoc($partCompanyId, $projectClassifyId, $bookTitleId, $dirStorage)
    {
        $bookTitle     = BookTitleService::getBookTitleInfo($partCompanyId, $projectClassifyId, $bookTitleId);
        $partCompany   = PartCompany::query()->find($partCompanyId);
        $project       = Project::query()->find($partCompany->project_id);
        $bookName      = self::getBookName($project->title, $bookTitle->title);
        $combinationId = (new ProjectService())->getDossierCombination($partCompanyId); //获取对应的案卷模板组合
        switch ($combinationId) { //通过判断，使用项目指定备考表模板
            case 2: //8号文
                $startEndDate      = self::getStartEndDate($bookTitle, 2);
                $templateProcessor = new TemplateProcessor('storage/words/doc_template/cover2.docx');
                break;
            default: //29号文
                $startEndDate      = self::getStartEndDate($bookTitle, 1);
                $templateProcessor = new TemplateProcessor('storage/words/doc_template/cover1.docx');
        }
        $templateProcessor->setValue('archives_no', $bookTitle->archives_no); //档案号
        $templateProcessor->setValue('part_company', $partCompany->title);//参建单位
        $templateProcessor->setValue('project_name', $project->title); //项目名称
        $templateProcessor->setValue('book_name', $bookName); //除去项目名的案卷题名内容
        $templateProcessor->setValue('period', $bookTitle->store_period); //期限
        $templateProcessor->setValue('construction', $bookTitle->construction_miles); //施工里程
        $templateProcessor->setValue('completed', $bookTitle->completed_miles); //已竣工里程
        $templateProcessor->setValue('date', $startEndDate); //起止日期

        $fileName = "{$dirStorage}/{$bookTitle->serial_no}";
        $savePath = "{$fileName}.docx";
        $templateProcessor->saveAs($savePath); //另存为

        return public_path() . "/{$savePath}"; //保存的路径
    }

    /**
     * 获取 bookName
     *
     * @param $projectName
     * @param $bookTitleName
     * @return string
     * @author: lxk
     * @date: 2025-07-18 9:23
     */
    private static function getBookName($projectName, $bookTitleName): string
    {
        return mb_substr($bookTitleName, strlen($projectName) / 3); //长度除以3是汉字长度
    }

    /**
     * 获取 获取需要的日期格式
     *
     * @param $bookTitle
     * @param int $template
     * @return string
     * @author: lxk
     * @date: 2025-07-18 9:24
     */
    private static function getStartEndDate($bookTitle, int $template = 1): string
    {
        $start = strtotime($bookTitle->start_date);
        $end   = strtotime($bookTitle->start_date);
        if ($template == 1) { //29号文格式
            return date('Y年m月', $start) . '至' . date('Y年m月', $end);
        }
        if ($template == 2) { //8号文格式
            return date('Ym', $start) . '-' . date('Ym', $end);
        }
    }

    /**
     * 盒子脊背
     *
     * @param $partCompanyId
     * @param $projectClassifyId
     * @param $bookTitleIds
     * @throws ApiException
     * @author: lxk
     * @date: 2025-07-17 15:45
     */
    public function exportBox($partCompanyId, $projectClassifyId, $bookTitleIds)
    {
        $fileDir = self::getDir($partCompanyId, $projectClassifyId, $bookTitleIds, 6);
        $docPath = "words/boxes/{$fileDir}"; //盒子脊背
        CommonService::createDir($docPath);
        $absStorageDir = storage_path('app/public/' . $docPath);
        foreach ($bookTitleIds as $bookTitleId) {
            $this->createBoxDoc($partCompanyId, $projectClassifyId, $bookTitleId, $absStorageDir); //封面盒子
        }
        CommonService::addProjectActionLog("导出{$fileDir}", $bookTitleIds[0], $partCompanyId, $projectClassifyId);

        return $this->mergeWord('storage/' . $docPath, 'boxes');
    }

    /**
     * 创建盒子封面
     *
     * @param $partCompanyId
     * @param $projectClassifyId
     * @param $bookTitleId
     * @param $dirStorage
     * @author: lxk
     * @date: 2025-07-17 15:46
     */
    public function createBoxDoc($partCompanyId, $projectClassifyId, $bookTitleId, $dirStorage)
    {
        $bookTitle     = BookTitleService::getBookTitleInfo($partCompanyId, $projectClassifyId, $bookTitleId);
        $partCompany   = PartCompany::query()->find($partCompanyId);
        $project       = Project::query()->find($partCompany->project_id);
        $bookName      = self::getBookName($project->title, $bookTitle->title); //除去项目名的案卷题名内容
        $combinationId = (new ProjectService())->getDossierCombination($partCompanyId); //获取对应的案卷模板组合
        switch ($combinationId) { //通过判断，使用项目指定备考表模板
            case 2: //使用案卷模板判断是8号文
                $result            = self::getCutTitleResult($bookName);
                $templateProcessor = new TemplateProcessor("storage/words/doc_template/box2_{$result['count']}.docx");
                for ($i = 1; $i <= $result['count']; $i++) {
                    $templateProcessor->setValue("book_name{$i}", $result["book_name{$i}"]); //除去项目名的案卷题名内容
                }
                break;
            case 5:
                $templateProcessor = new TemplateProcessor('storage/words/doc_template/box3.docx');
                $templateProcessor->setValue('book_name', $bookName);
                break;
            default:
                $templateProcessor = new TemplateProcessor('storage/words/doc_template/box1.docx');
                $templateProcessor->setValue('book_name', $bookName);
        }
        $startEndDate = self::getStartEndDate($bookTitle, 1);

        $fondsNumber = Company::query()->where('id', company_id())->value('fonds_number');

        $templateProcessor->setValue('archives_no', $bookTitle->archives_no); //档案号
        $templateProcessor->setValue('construction', $bookTitle->construction_miles); //施工里程
        $templateProcessor->setValue('completed', $bookTitle->completed_miles); //已竣工里程
        $templateProcessor->setValue('period', $bookTitle->store_period); //期限
        $templateProcessor->setValue('project_name', $project->title); //项目名称
        $templateProcessor->setValue('part_company', $partCompany->title);//参建单位
        $templateProcessor->setValue('date', $startEndDate); //起止日期
        $templateProcessor->setValue('book_title', $bookTitle->title); //完整的案卷材料题名
        //下面是档案号分割部分
        $archivesNo = self::formatArchivesNo($bookTitle->archives_no);
        $templateProcessor->setValue('fonds_number', $fondsNumber); //全宗号
        $templateProcessor->setValue('first', $archivesNo['first']); //档案部分1
        $templateProcessor->setValue('second', $archivesNo['second']); //档案部分2
        $templateProcessor->setValue('three', $archivesNo['three']); //档案部分3
        $templateProcessor->setValue('four', $archivesNo['four']); //档案部分4
        $templateProcessor->setValue('five', $archivesNo['five']); //档案部分5

        $fileName = "{$dirStorage}/$bookTitle->serial_no";
        $savePath = "{$fileName}.docx";
        $templateProcessor->saveAs($savePath); //另存为
    }

    /**
     * 分割案卷题名，根据长度，分割成 1到 7组，按照需求选择指定模板
     * cut_str_same_long() ，对一个字符串截取指定长度
     * @param $title
     * @return array
     */
    public static function getCutTitleResult($title): array
    {
        $bookName1 = cut_str_same_long($title, 16);   //截取一段

        $title     = str_replace($bookName1, "", $title); //截取后去掉
        $bookName2 = cut_str_same_long($title, 16);   //接着截取下一段

        $title     = str_replace($bookName2, "", $title); //然后再去掉
        $bookName3 = cut_str_same_long($title, 16);

        $title     = str_replace($bookName3, "", $title);
        $bookName4 = cut_str_same_long($title, 16);

        $title     = str_replace($bookName4, "", $title);
        $bookName5 = cut_str_same_long($title, 16);

        $title     = str_replace($bookName5, "", $title);
        $bookName6 = cut_str_same_long($title, 16);

        $title     = str_replace($bookName6, "", $title);
        $bookName7 = cut_str_same_long($title, 16);

        $arr[] = $bookName1;
        $arr[] = $bookName2;
        $arr[] = $bookName3;
        $arr[] = $bookName4;
        $arr[] = $bookName5;
        $arr[] = $bookName6;
        $arr[] = $bookName7;

        return [ //我们假定标题最多为 7 行
            'count'      => count(array_filter($arr)), //去掉空数组，再计算数量
            'book_name1' => $bookName1,
            'book_name2' => $bookName2,
            'book_name3' => $bookName3,
            'book_name4' => $bookName4,
            'book_name5' => $bookName5,
            'book_name6' => $bookName6,
            'book_name7' => $bookName7,
        ];
    }

    /**
     * 格式化档案号，把档案号分割成多个字符，共 5 部分 (盒子封面用)
     * 例如有几种格式：
     *      J•SJKZ•5ZH123/169-67 =>  1、J ； 2、SJKZ ； 3、5ZH ； 4、123/169 ； 5、67
     *      K•CKKZ•5R123/169-68  =>  1、K ； 2、CKKZ ； 3、5R  ； 4、123/169 ； 5、68
     *      J•CKKZ•5DH123J-1     =>  1、J ； 2、CKKZ ； 3、5DH ； 4、123J    ； 5、1
     *      J•CKKZ•1ZH-1         =>  1、J ； 2、CKKZ ； 3、1ZH ； 4、''      ； 5、1
     * 说明： • 必须为中文，英文解析不到，• utf-8编码下，占 3个字节
     */
    private static function formatArchivesNo($archivesNo)
    {
        if (strpos($archivesNo, '·')) { //有的档案号用 · 分割的，不同于 •
            $archivesNo = str_replace('·', '•', $archivesNo); //替换掉
        }
        $p1 = strpos($archivesNo, '•');   //第1个 • 的位置
        $p2 = strripos($archivesNo, '•'); //第2个 • 的位置
        $p3 = strripos($archivesNo, '-'); //'-'出现的位置

        $first   = substr($archivesNo, 0, $p1); //【第1部分】
        $second  = substr($archivesNo, $p1 + 3, $p2 - 4); //【第2部分】
        $letter  = substr($archivesNo, $p2 + 5, 1); //第二个 • 之后的第三个字符
        $space   = ctype_upper($letter) ? 3 : 2; //通过是否是一个大写字母，给出字符间隔
        $three   = substr($archivesNo, $p2 + 3, $space); //【第3部分】
        $tmp     = $p2 + $space;
        $fourLen = $p3 - $tmp - 3; //第三部分之后和'-'之前的内容长度
        $four    = substr($archivesNo, $tmp + 3, $fourLen); //【第4部分】
        $five    = substr($archivesNo, $p3 + 1); //【第5部分】

        return [
            'first'  => $first,
            'second' => $second,
            'three'  => $three,
            'four'   => $four,
            'five'   => $five,
        ];
    }

    /**
     * 合并多个 word 为一个新的word
     *
     * @param $dirStorage
     * @param null $content
     * @param string $fileType
     * @return array|string
     * @throws CopyFileException
     * @throws CreateTemporaryFileException
     * @author: lxk
     * @date: 2025-07-18 15:16
     */
    public static function mergeWord($dirStorage, $dirType, $content = null, string $fileType = 'docx')
    {
        $tmpDir            = str_replace('storage/', '', $dirStorage); // words/boxes/机场土建1标_脊背_案卷序号1~10
        $wordDocumentPaths = Storage::disk('public')->files($tmpDir);
        $wordDocumentCount = count($wordDocumentPaths);
        if ($wordDocumentCount < 1) {
            throw new ApiException('出现错误了！文件没生成');
        } elseif (count($wordDocumentPaths) == 1) {
            $fileName = str_replace("storage/words/{$dirType}/", '', $dirStorage);
            $from     = '/public/' . $wordDocumentPaths[0];
            $to       = "/public/words/{$dirType}/{$fileName}.docx";
            if ($content) $to = "/public/words/{$dirType}/{$content}/{$fileName}.docx";
            if (Storage::exists($to)) Storage::delete($to); //如果文件已存在，删除
            Storage::move($from, $to); //把文件从文件夹移除来
            Storage::deleteDirectory('/public/words/boxes/' . $fileName); //删除空文件夹
            $downloadUrl = config('site.domain') . $dirStorage . '.docx';
            $fileInfo    = pathinfo($downloadUrl);
        } else {
            natsort($wordDocumentPaths); //按照文件名数值大小自然排序
            $mainTemplateProcessor = new OpenTemplateProcessor('storage/' . $wordDocumentPaths[0]);
            $allContent            = '';
            unset($wordDocumentPaths[0]);
            foreach ($wordDocumentPaths as $wordDocumentPath) { // 拓展类就是为了拿到他的xml
                $innerTemplateProcessor = new OpenTemplateProcessor('storage/' . $wordDocumentPath);
                $innerXml               = $innerTemplateProcessor->tempDocumentMainPart;
                if (preg_match('/<w:body>(.*)<\/w:body>/s', $innerXml, $matches)) {  // 提取body内容并清理
                    $innerContent = $matches[1];
                    $innerContent = self::cleanWordContent($innerContent); // 清理内容：移除末尾的空段落和分页符
                    $allContent   .= $innerContent;
                }
            }
            $mainXml = $mainTemplateProcessor->tempDocumentMainPart;
            // 清理主文档body结束前的内容，避免原文档末尾的空白
            $mainXml = preg_replace('/(.*)<\/w:body>/s', '$1', $mainXml);
            // 合并内容，不添加额外的分页符
            $mainXml                                     .= $allContent . '</w:body>';
            $mainTemplateProcessor->tempDocumentMainPart = $mainXml;

            $endSlashPos    = strrpos($dirStorage, '/');
            $mergedFileName = substr($dirStorage, $endSlashPos) . ".{$fileType}";
            $newDirStorage  = substr($dirStorage, 0, $endSlashPos);
            if ($content) $newDirStorage = substr($dirStorage, 0, $endSlashPos) . "/{$content}";
            $downloadDir = $newDirStorage . $mergedFileName;
            $mainTemplateProcessor->saveAs($downloadDir);
            $needDelDir = str_replace('storage', '/public', $dirStorage);

            Storage::deleteDirectory($needDelDir);
            $downloadUrl = config('site.domain') . $downloadDir;
            $fileInfo    = pathinfo($downloadUrl);
        }

        return [
            'basename'     => $fileInfo['basename'], //脊背_案卷序号【2~5】.zip
            'download_url' => $downloadUrl //如果有井号键，去掉，否则会下载失败
        ];
    }

    /**
     * 清理Word文档内容，去除多余的空白段落和分页符
     */
    private static function cleanWordContent($content)
    {
        // 移除分页符
        $content = preg_replace('/<w:br\s+w:type="page"\/>/i', '', $content);
        $content = preg_replace('/<w:lastRenderedPageBreak\/>/i', '', $content);

        // 移除空段落
        $content = preg_replace('/<w:p[^>]*>\s*<w:r[^>]*>\s*<w:t[^>]*>\s*<\/w:t>\s*<\/w:r>\s*<\/w:p>/i', '', $content);
        $content = preg_replace('/<w:p[^>]*>\s*<\/w:p>/i', '', $content);

        // 移除连续的空白段落
        return preg_replace('/(<w:p[^>]*>\s*<\/w:p>\s*)+/i', '', $content);
    }

    /**
     * 获取python的执行环境
     *
     * @return string
     * @author: lxk
     * @date: 2025-07-02 17:43
     */
    public static function getPythonEnv(): string
    {
        $pythonEnv = '/www/server/pyporject_evn/versions/3.10.16/bin/python3'; //生产环境
        if (config('site.domain') != 'https://tielu.qichenglantai.com/') {  //开发环境
            $pythonEnv = 'D:\phpstudy_pro\WWW\MainGongLu\LvPHP\public\python_script\venv\Scripts\python.exe';
        }

        return $pythonEnv;
    }

    /**
     * 调用Python脚本处理excel，并返回处理过的文件路径
     *
     * @param $process
     * @param $typeName
     * @return array
     * @throws ApiException
     * @author: lxk
     * @date: 2025-07-17 16:04
     */
    public function dealByPython($process, $typeName): array
    {
        $process->setTimeout(7200); //python脚本执行超时时间秒
        $process->run();
        if (!$process->isSuccessful()) {
            info("python处理{$typeName}失败---" . $process->getErrorOutput());
            throw new ApiException($process->getErrorOutput());
        }
        $storagePath = preg_replace("/\r?\n/", "", $process->getOutput());
        return [
            'basename'     => pathinfo($storagePath)['basename'],
            'download_url' => config('site.domain') . $storagePath
        ];
    }

    /**
     * 获取文件夹路径
     *
     * @param $partCompanyId
     * @param $projectClassifyId
     * @param $bookTitleIds
     * @param $type
     * @return string
     * @throws ApiException
     * @author: lxk
     * @date: 2025-07-17 15:42
     */
    public static function getDir($partCompanyId, $projectClassifyId, $bookTitleIds, $type): string
    {
        $bookTitleInstance    = CommonService::getBookTitleInstance($partCompanyId, $projectClassifyId);
        $serialNos            = CommonService::getChildSerialNos($bookTitleInstance, to_array($bookTitleIds));
        $partCompany          = PartCompany::query()->where('id', $partCompanyId)->first();
        $project              = Project::query()->where('id', $partCompany->project_id)->first();
        $projectClassifyTitle = ProjectClassify::query()->where('id', $projectClassifyId)->value('title');
        $partCompanyName      = self::simplePartCompanyName($partCompany->title);
        $typeName             = match ($type) {
            1 => '案卷目录',
            2 => '卷内目录',
            3 => '全引目录',
            4 => '备考表',
            5 => '案卷封面',
            6 => '盒子脊背',
            default => '未知类型',
        };

        $content = "{$project->document_number}_{$partCompanyName}_{$projectClassifyTitle}_{$typeName}_{$serialNos}_" . date('His');

        return get_valid_path($content);
    }

    /**
     * 简化参建单位名称
     *
     * @param string $title
     * @return string
     * @author: lxk
     * @date: 2025-07-17 14:37
     */
    public static function simplePartCompanyName(string $title): string
    {
        if (str_contains($title, '集团')) {
            $title = str_replace('集团', '', $title);
        }
        if (str_contains($title, '股份')) {
            $title = str_replace('股份', '', $title);
        }
        if (str_contains($title, '责任')) {
            $title = str_replace('责任', '', $title);
        }
        if (str_contains($title, '有限')) {
            $title = str_replace('有限', '', $title);
        }
        if (str_contains($title, '公司')) {
            $title = str_replace('公司', '', $title);
        }

        return $title;
    }

    /**
     * 使用Python处理单元格
     *
     * @param $projectId
     * @param $dir
     * @return string
     * @author: lxk
     * @date: 2025-08-15 16:58
     */
    public static function dealExcelByPython($projectId, $dir): string
    {
        $combinationId = Project::query()->where('id', $projectId)->value('combination_id');
        if ($combinationId == 2) {
            if (in_array($projectId, [5, 15, 16])) { //京石公司8号文专用
                $pythonScript = 'python_script/8_catalog_js.py';
            } else {
                $pythonScript = 'python_script/8_catalog.py';
            }
        } elseif ($combinationId == 3) {
            $pythonScript = 'python_script/ly_catalog.py'; //兰渝格式
        } elseif ($combinationId == 4) {
            $pythonScript = 'python_script/bz_29_catalog.py'; //标准29号文格式
        } else {
            $pythonScript = 'python_script/29_catalog.py'; //29号文
        }
        $pythonEnv = self::getPythonEnv();
        $process   = new Process([$pythonEnv, $pythonScript, 'storage/' . $dir, 3000]); //交给python进行合成
        $process->setTimeout(7200); //python脚本执行超时时间秒
        $process->run();
        if (!$process->isSuccessful()) {
            info('python处理excel失败---' . $process->getErrorOutput());
            return '';
        } else {
            $downloadDir = $process->getOutput();
            return preg_replace("/\r?\n/", "", $downloadDir); //去掉可能的换行符 如 \n
        }
    }


}
